#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <syslog.h>
#include "./dbHandler/dbHandler.h"
#include "../error/error.h"
#include "../clsv/clsv.h"
#include "../cypher/cypher.h"
#include "../translation/hton.h"
#include "../translation/ntoh.h"
#include "../tickets/tickets.h"
#include "../cypher/cypher.h"
#include "../sockcomADT/sockcomADT.h"
#include "server.h"
#include "svstub.h"
#include "conf.h"


#define	HOST			"localhost"
#define	PORT			"4500"

#define	FILE_BUFFER		MAX_DATA

Comm comm;

static pid_t servpid;

serverInfoT serverInfo;

/* performs authentication of the user */
static int check_login(DATA * p, OPC opcode);

/* process the login command */
static void process_login(DATA * p, int login, int service);

/* hands out a ticket, if the user is entitled to one */
static void process_get_ticket(DATA * p, int login, int service);

/* determines if a ticket can be cashed. if so a download is started */
static void process_use_ticket(DATA * p, int login, int service);

/* determines if and upload can be authorized. if so, it starts */
static void process_upload(DATA * p, int login, int service);

/* changes the password of a logged in user */
static void process_passwd(DATA * p, int login, int service);

/* list categories */
static void process_list_categories(DATA * p, int login, int service);

/* list users */
static void process_list_users(DATA * p, int login, int service);

/* list files for a given category */
static void process_list_files(DATA * p, int login, int service);


/* trasport stream buffer */
static CLSV_BUFFER clsv_buff;

static void (*proc[])(DATA * p, int login, int service) =
{
	process_login,
	process_passwd,
	process_get_ticket,
	process_use_ticket,
	process_upload,
	process_list_categories,
	process_list_users,
	process_list_files
};

/* returns status of received packet */
static int
recv_only(CLSV_BUFFER * p, OPC opcode, int service)
{
	int bytes;
	void * stream;
	STAT status;
	
	/* allocate memory for transport stream */
	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return -1;
	}
	
	/* receive reply from server and convert stream to corresponding struct */
	if ((bytes = receive_packet(comm, stream, sizeof(CLSV_BUFFER),
								service)) < 0)
		return bytes;
	if( errno == EAGAIN )			/* If read times out */
	{
		errno= 0;
		return -ERROR_TIME_OUT;
	}
	
	/* copy status so it can be returned */
	memcpy((void *)&status, (void *)((char *)stream+sizeof(OPC)),
		   sizeof(STAT));
	status = ntohs(status);
	ntoh_clsv_buffer(&p, stream, CL);
	
	/* free stream and return */
	free(stream);
	return status;
}

static int
send_only(CLSV_BUFFER * p, OPC opcode, int service)
{
	int bytes;
	void * stream;

	/* allocate memory for transport stream */
	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
	{
		fprintf(stderr, "Memory allocation error\n");
		return -1;
	}

	/* convert struct to stream and send it to the server */
	bytes = hton_clsv_buffer(p, &stream, opcode, SV);
	if( service == _TCP_ )
		send_packet(comm, stream, bytes, _TCP_);
	else
		send_UDP(comm, stream, bytes);

	free(stream);

	return bytes;
}

static int
check_login(DATA * p, OPC opcode)
{
	STAT status;
	char user_pass[MAX_PASS+1], user[MAX_USER+1], pass[MAX_EPASS];
	
	/* extract user and password */
	memcpy((void *)user, (void *)p, MAX_USER+1);
	memcpy((void *)pass, (void *)((char *)p+MAX_USER+1), MAX_EPASS);
	
	if ((status = GetPass(serverInfo.db, user_pass, user)) < 0)
		return status;
	
	if (decypher2(pass, MAX_EPASS, user_pass) < 0)
		return -ERROR_WRONG_LOGIN;
	
	return do_checklogin(serverInfo.db, pass, user);
}

static void
process_login(DATA * p, int login, int service)
{
	CLSV_BUFFER * p_buff = &clsv_buff;
	
	if (login < 0)
		p->svcl_login.status = -ERROR_WRONG_LOGIN;
	else
		p->svcl_login.status = OK;
	
	send_only(p_buff, LOGIN, service);
}

static void
process_get_ticket(DATA * p, int login, int service)
{
	ticketP tickp;
	CLSV_BUFFER * p_buff = &clsv_buff;
	
	int ret;
	char curr_pass[MAX_PASS+1];
	char * user;
	time_t now;
	
	/* get username */
	getUsername(serverInfo.db, login, &user);
	
	if (login < 0)
	{
		p->svcl_get_ticket.status = login;
		send_only(p_buff, GET_TICKET, service);
		return;
	}
	
	/* get password */
	if (GetPass(serverInfo.db, curr_pass, p->clsv_get_ticket.user) < 0)
	{
		p->svcl_get_ticket.status = -ERROR_SERVER_CRITIC;
		send_only(p_buff, GET_TICKET, service);
		return;
	}
	
	if (decypher2(p->clsv_get_ticket.filename, MAX_FILENAME, curr_pass) < 0)
	{
		p->svcl_get_ticket.status = -ERROR_SERVER_CRITIC;
		send_only(p_buff, GET_TICKET, service);
		return;
	}
	
	if ((ret = get_ticket(p->clsv_get_ticket.filename, &tickp, login)) < 0)
	{
		p->svcl_get_ticket.status = ret;
		send_only(p_buff, GET_TICKET, service);
		return;
	}
	
	/* if everything is OK, copy ticket info to send to client */
	p->svcl_get_ticket.status = tickp.retries;
	p->svcl_get_ticket.dtime = tickp.dtime;
	p->svcl_get_ticket.etime = tickp.etime;
	p->svcl_get_ticket.ftime = tickp.ftime;
	strcpy(p->svcl_get_ticket.id, tickp.id);
	if (cypher2(p->svcl_get_ticket.id, MAX_TICKET, curr_pass) < 0)
		p->svcl_get_ticket.status = -ERROR_SERVER_CRITIC;
	
	/* send reply to client and log event*/
	send_only(p_buff, GET_TICKET, service);
	time(&now);
	syslog (LOG_NOTICE, "Ticket handed to user %s at %s", user, ctime(&now));
	
	free(user);
	return;
}

static void
process_use_ticket(DATA * p, int login, int service)
{
	CLSV_BUFFER * p_buff = &clsv_buff;
	ticketP tickp;
	
	int ret;
	char curr_pass[MAX_PASS+1];
	char filename[MAX_FILENAME+10];
	char auxFilename[MAX_FILENAME+10];
	char user[MAX_USER+1];
	
	void * stream;
	FILE * file;
	char * buffer[FILE_BUFFER], * ip;
	int qty, bytes;
	
	if (login < 0)
	{
		p->comm_data_block.status = login;
		send_only(p_buff, DATA_BLOCK, service);
		return;
	}
	
	/* get password */
	if ((ret = GetPass(serverInfo.db, curr_pass, p->clsv_use_ticket.user) < 0))
	{
		p->comm_data_block.status = ret;
		send_only(p_buff, DATA_BLOCK, service);
		return;
	}
	strcpy(user, p->clsv_use_ticket.user);
	
	/* decypher requested ticket id */
	if ((ret = decypher2(p->clsv_use_ticket.ticket_id, MAX_ETICKET,
						 curr_pass)) < 0)
	{
		p->comm_data_block.status = ret;
		send_only(p_buff, DATA_BLOCK, service);
		return;
	}
	
	/* I ensure it ends with '\0' */
	p->clsv_use_ticket.ticket_id[MAX_TICKET] = '\0';
	
	
	if ((ret = use_ticket(p->clsv_use_ticket.ticket_id, &tickp, login)) < 0)
	{
		p->comm_data_block.status = ret;
		send_only(p_buff, DATA_BLOCK, service);
		return;
	}
	
	getFilenameByID(serverInfo.db, p->clsv_use_ticket.ticket_id, filename);
	
	/* allocate memory for travelling stream */
	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
		fprintf(stderr, "Error allocating memory\n");
	
	
	/* look in files dir */
	sprintf(auxFilename, "%s%s", "files/", filename);
	
	/* open local file */
	if ((file = fopen(auxFilename, "rb")) == NULL)
	{
		p->comm_data_block.status = -ERROR_FILE_NOT_EXISTS;
		send_only(p_buff, DATA_BLOCK, service);
		return;
	}
	
	/* Logg which user is downloading which file */
	ip= printIP(comm);
	logConn(serverInfo.db, login, ip, filename, _DOWNLOAD_);
	
	/* send file */
	while ((qty = fread(buffer, 1, FILE_BUFFER, file)) > 0)
	{
		/* build struct to send */
		p->comm_data_block.status = qty;
		memcpy(p->comm_data_block.data, buffer, qty);
		/* convert struct to stream and send reply */
		bytes = hton_clsv_buffer(&clsv_buff, &stream, DATA_BLOCK, SV);
		send_packet(comm, stream, bytes, service);
	}
	/* Unlogg the user */
	unlogConn(serverInfo.db, login, ip, filename);
	
	/* log event */
	syslog (LOG_NOTICE, "User %s downloaded %s", user, filename);
	
	/* send packet with status = 0 */
	p->comm_data_block.status = 0;
	send_only(p_buff, DATA_BLOCK, service);
	
	free(stream);
	return;
}

static void
process_upload(DATA * p, int login, int service)
{
	CLSV_BUFFER * p_buff = &clsv_buff;
	char filename[MAX_FILENAME + 1], aux[MAX_FILENAME + 10];
	char * ip;
	int size;
	short allowed;
	STAT status;
	
	FILE * file;
	char buffer[FILE_BUFFER];
	int tot = 0;
	
	size = p->clsv_upload.size;
	size = ntohl(size);
	strcpy(filename, p->clsv_upload.filename);
	
	/* change the saving location to files/ directory */
	sprintf(aux, "%s%s", "files/", filename);
	
	/* is this upload permitted? */
	allowed = upload(filename, size);
	
	/* send reply to client */
	p->svcl_upload.status = allowed;
	send_only(p_buff, UPLOAD, service);
	
	/* if upload was not authorized, return */
	if (allowed < 0)
		return;
	
	/* upload accepted, open file to upload */
	if ((file = fopen(aux, "wb")) == NULL)
	{
		p->comm_data_block.status = -ERROR_SERVER_CRITIC;
		send_only(p_buff, DATA_BLOCK, service);
		return;
	}
	
	/* Logg which user is uploading which file */
	ip = printIP(comm);
	logConn(serverInfo.db, login, ip, aux, _UPLOAD_);
	
	status = allowed;
	/* receive file */
	while (status > 0 && tot <= size)
	{
		if ((status = recv_only(&clsv_buff, DATA_BLOCK, service)) > 0)
		{
			tot += status;
			memcpy(buffer, p->comm_data_block.data, status);
			fwrite(buffer, 1, status, file);
		}
	}

	/* Unlogg the user */
	unlogConn(serverInfo.db, login, ip, aux);
	
	
	if (status < 0)
	{
		/* an error ocurred, inform client */
		p->comm_data_block.status = -ERROR_SERVER_CRITIC;
		send_only(p_buff, DATA_BLOCK, service);
		fclose(file);
		return;
	}
	else
	{
		/* add file to database and log event */
		addFile(serverInfo.db, filename, size);
		serverInfo.load += size;
		syslog(LOG_NOTICE, "%s uploaded %s to server", ip, filename);
		/* send ACK to client */
		p->svcl_upload.status = 1;
		send_only(p_buff, UPLOAD, service);
	}
	
	fclose(file);
	
	return;
}

static void
process_passwd(DATA * p, int login, int service)
{
	STAT status;
	CLSV_BUFFER * p_buff = &clsv_buff;
	char current_pass[MAX_PASS+1];
	
	if (login < 0)
	{
		p->svcl_passwd.status = login;
		send_only(p_buff, PASSWD, service);
		return;
	}
	
	/* get password to decypher user passwords */
	if ((status = GetPass(serverInfo.db, current_pass, p->clsv_passwd.user)) < 0)
	{
		p->svcl_passwd.status = status;
		send_only(p_buff, PASSWD, service);
		return;
	}
	
	/* decypher old and new passwords */
	if ((status = decypher2(p->clsv_passwd.oldpass, MAX_EPASS,
							current_pass)) < 0)
	{
		p->svcl_passwd.status = status;
		send_only(p_buff, PASSWD, service);
		return;
	}
	if ((status = decypher2(p->clsv_passwd.newpass, MAX_EPASS,
							current_pass)) < 0)
	{
		p->svcl_passwd.status = status;
		send_only(p_buff, PASSWD, service);
		return;
	}
	
	/* change password and send reply to client */
	p->svcl_passwd.status =
	passwd(p->clsv_passwd.oldpass, p->clsv_passwd.newpass, login);
	send_only(p_buff, PASSWD, service);
	
	return;
}

static void
process_list_categories(DATA * p, int login, int service)
{
	STAT status;
	CLSV_BUFFER * p_buff = &clsv_buff;
	
	int bytes;
	char * list;
	char * off;
	
	/* get category list */
	bytes = list_categories(&list);

	/* send list */
	off = list;
	while (bytes > 0)
	{
		if (bytes < MAX_LIST)
			status = bytes;
		else
			status = MAX_LIST;
		bytes -= status;
		p->comm_list_block.status = status;
		memcpy(p->comm_list_block.data, off, status);
		send_only(p_buff, LIST_BLOCK, service);
		off = off+status;
	}
	/* send packet with status = 0 (or error, if any ocurred) */
	p->comm_list_block.status = bytes;
	send_only(p_buff, LIST_BLOCK, service);
	
	free(list);
	return;
}

static void
process_list_users(DATA * p, int login, int service)
{	
	STAT status;
	CLSV_BUFFER * p_buff = &clsv_buff;
	
	int bytes;
	char * list;
	char * off;
	
	if (login < 0)
	{
		p->comm_list_block.status = login;
		send_only(p_buff, LIST_BLOCK, service);
		return;
	}
	else
	{
		/* get category list */
		bytes = list_users(&list);
		
		/* send list */
		off = list;
		while (bytes > 0)
		{
			if (bytes < MAX_LIST)
				status = bytes;
			else
				status = MAX_LIST;
			bytes -= status;
			p->comm_list_block.status = status;
			memcpy(p->comm_list_block.data, off, status);
			send_only(p_buff, LIST_BLOCK, service);
			off = off+status;
		}
		/* send packet with status = 0 (or error, if any ocurred) */
		p->comm_list_block.status = bytes;
		send_only(p_buff, LIST_BLOCK, service);
		
		return;
	}
}

static void
process_list_files(DATA * p, int login, int service)
{	
	STAT status;
	CLSV_BUFFER * p_buff = &clsv_buff;
	
	int bytes;
	char * list;
	char * off;
	char curr_pass[MAX_PASS+1];
	
	if (login < 0)
	{
		p->comm_list_block.status = login;
		send_only(p_buff, LIST_BLOCK, service);
		return;
	}
	else
	{
		/* get category list */
		bytes = list_files(&list, p->clsv_list_files.category);
		
		/* get password */
		if (GetPass(serverInfo.db, curr_pass, p->clsv_get_ticket.user) < 0)
		{
			p->svcl_get_ticket.status = -ERROR_SERVER_CRITIC;
			send_only(p_buff, GET_TICKET, service);
			return;
		}
	
		/* send list */
		off = list;
		while (bytes > 0)
		{
			if (bytes < MAX_LIST)
				status = bytes;
			else
				status = MAX_LIST;
			bytes -= status;
			printf("status:%d\n", status);
			p->comm_list_block.status = status;
			memcpy(p->comm_list_block.data, off, status);
			cypher2(p->comm_list_block.data, 24, curr_pass);
			send_only(p_buff, LIST_BLOCK, service);
			off = off+status;
		}
		/* send packet with status = 0 (or error, if any ocurred) */
		p->comm_list_block.status = bytes;
		send_only(p_buff, LIST_BLOCK, service);
		
		return;
	}
}

int
start_session(Comm comm, commType com, char * host, char * port)
{
    return initComm(comm, com, host, port);
}

void
do_server(Comm comm)
{
	int sock;

	if ((sock= acceptComm(comm)) < 0)
	{
		if (errno != EINTR)
			fprintf(stderr, "Error: accept: %s\n", strerror(errno));
		return;		
	}
	signal(SIGCHLD, reaper);

	switch(fork())
	{
		case 0:					/* child */
			closeComm(comm, SERVER);
			process_server(comm);
			exit(1);
		default:				/* parent */
			closeComm(comm, CLIENT);
			break;
		case -1:
			fprintf(stderr, "Error: fork: %s\n", strerror(errno));
	}
	
}

void
process_server(Comm comm)
{
	int login = 0;
	OPC opcode;
	CLSV_BUFFER * p = &clsv_buff;
	void * stream;
	
	srand(time(NULL));
	
	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
		fprintf(stderr, "Error allocating memory\n");
	
	/* receive packet from client and convert stream to corresponding struct */
	receive_packet(comm, stream, sizeof(CLSV_BUFFER), _TCP_);
	if( errno == EAGAIN )					/* If read times out */
	{
		errno= 0;
		print_error(-ERROR_TIME_OUT);
	}

	ntoh_clsv_buffer(&p, stream, CL);
	opcode = p->opcode;
	
	/* process requested operation */
	if (opcode >= 0 && opcode < MAX_OPCODES)
	{
		/* check login for functions that require user authentication */
		if (opcode == LOGIN || opcode == PASSWD || opcode == GET_TICKET || 
			opcode == USE_TICKET || opcode == LIST_USERS || opcode == LIST_FILES)
			login = check_login(&(p->data), opcode);
		
		(*proc[opcode])(&(p->data), login, _TCP_);
		
	}
	else
	/* unrecognized opcode */
		fprintf(stderr, "Error: Incorrect opcode\n");
	
	free(stream);
}

void
takeUDPResquest(Comm comm)
{
	CLSV_BUFFER * p = &clsv_buff;
	void * stream;

	if ((stream = malloc(sizeof(CLSV_BUFFER))) == NULL)
		fprintf(stderr, "Error allocating memory\n");

	receive_UDP(comm, stream, sizeof(CLSV_BUFFER));
	ntoh_clsv_buffer(&p, stream, CL);

	process_UDP(p);

	free(stream);
	return;
}

void
process_UDP(CLSV_BUFFER * p)
{
	int login= 0;

	if(p->opcode == PASSWD)
		login= checkID(serverInfo.db, p->data.clsv_passwd.user);
		
	if(p->opcode == GET_TICKET)
		login= checkID(serverInfo.db, p->data.clsv_get_ticket.user);

	if( p->opcode == PASSWD || p->opcode == GET_TICKET || 
			p->opcode == LIST_CATEGORIES )
		(*proc[p->opcode])(&(p->data), login, _UDP_);
	return;
}

void 
siginthandler(int sig)
{
	if( servpid == getpid() )
	{
		printf("Stoping server...\t\t\t");
		closeComm(comm, SERVER);
		printf("[\x1b[%d;%dmOK\x1b[0m]\n", 0, 32);
	}
	else
		closeComm(comm, CLIENT);
	exit(1);
}

void
sigusr1handler(int sig)
{
	
	if( servpid == getpid() )
	{
		system("clear");
		printf("Active Connections:\n\n\nIP\t\tUser\tFile\tStatus\n");
		printf("-------------------------------------------------------\n\n");
		printCons(serverInfo.db);
		printf("\n\n\nServer Running! (Press 'Ctrl+C' to stop it or send "
				"SIGUSR1 to PID %d to view active connections)\n", servpid);
	}
	return;	
}

void
reaper(int sig)
{
	int status;

	while (wait3(&status, WNOHANG, (struct rusage *)0) > 0);
}

int
main(int argc, char * argv[])
{
	int port[MAX_PORTS] = {0};		/* ports in which the server will listen */
	char ip[MAX_IP_LEN];		/* IP in which the server will listen*/
	int i, n;
	char sport[10];
	
	/* set seed for determining ticket times */
	srand(time(NULL));
	
	servpid = getpid();
	
	system("clear");
	
	/* get configuration */
	printf("Loading Configuration...\t\t");
	if (argc == 1)
		n = getConf(CONF_FILE, port, ip, &(serverInfo.connections), 
					serverInfo.db, &(serverInfo.capacity));
	else
	{
		n = parseConf(argc, argv, port, ip, &(serverInfo.connections), 
				  serverInfo.db, &(serverInfo.capacity));
		
		if(!n)
		{
			printf("\nError in arguments passed to server...\n");
			return 1;
		}
	}
	
	/* determine server's load */
	serverInfo.load = getServerLoad(serverInfo.db);
	
	printf("[\x1b[%d;%dmOK\x1b[0m]\n", 0, 32);
	
	/* check database integrity */
	printf("Checking Database Integrity...\t\t");
	if (checkDB(serverInfo.db))
	{
		printf("\x1b[%d;%dmDB Error!\x1b[0m\n", 0, 31);
		return 1;
	}
	printf("[\x1b[%d;%dmOK\x1b[0m]\n", 0, 32);
	purgeDB(serverInfo.db);		/* Purge DB in case of wrong closing */
	
	/* open syslog */
	openlog ("fish pro", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
	
	/* initialize connnection(s) */
	comm= newComm();
	for (i=0; i<n; i++)
	{
		sprintf(sport, "%d", port[i]);
		if (start_session(comm, COMM_SERVER, ip, sport) < 0)
			return 1;
	}
	printf("Starting Server...\t\t\t");
	if( comm == NULL )
	{
		printf("\x1b[%d;%dmFAIL!\x1b[0m\n", 0, 31);
		return 1;
	}
	printf("[\x1b[%d;%dmOK\x1b[0m]\n\nServer Running! (Press 'Ctrl+C' to stop "
		   "it or send SIGUSR1 to PID %d to view active connections)\n", 
		   0, 32, servpid);
	
	/* interruption handlers */
	signal(SIGINT, siginthandler);
	signal(SIGUSR1, sigusr1handler);
	
	forever
	{
		switch (sockSel(comm))
		{
			case _TCP_:
				do_server(comm); break;
			case _UDP_:
				takeUDPResquest(comm); break;
			default: break;
		}
	}
	
	/* close syslog */
	closelog ();
	
	return 0;
}
